/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
module dwt.internal.image.pngfileformat;


private import dwt.dwt;

private import dwt.graphics.imagedata;
private import dwt.graphics.imageloaderevent;
private import dwt.graphics.palettedata;
private import dwt.graphics.rgb;
private import dwt.internal.converter;
private import dwt.internal.compatibility;
private import dwt.internal.image.fileformat;
private import dwt.internal.image.ledatainputstream;
private import dwt.internal.image.ledataoutputstream;
private import dwt.util.javatypes;
private import dwt.util.util;



class PngChunk {

	byte[] reference;

	static const int LENGTH_OFFSET = 0;
	static const int TYPE_OFFSET = 4;
	static const int DATA_OFFSET = 8;
           
	static const int TYPE_FIELD_LENGTH = 4;
	static const int LENGTH_FIELD_LENGTH = 4;
	static const int MIN_LENGTH = 12;
            
	static const int CHUNK_UNKNOWN = -1;
	// Critical chunks.
	static const int CHUNK_IHDR = 0;
	static const int CHUNK_PLTE = 1;
	static const int CHUNK_IDAT = 2;
	static const int CHUNK_IEND = 3;
	// Non-critical chunks.
	static const int CHUNK_tRNS = 5;
	
	static const byte[] TYPE_IHDR = [cast(byte) 'I', cast(byte) 'H', cast(byte) 'D', cast(byte) 'R'];
	static const byte[] TYPE_PLTE = [cast(byte) 'P', cast(byte) 'L', cast(byte) 'T', cast(byte) 'E'];
	static const byte[] TYPE_IDAT = [cast(byte) 'I', cast(byte) 'D', cast(byte) 'A', cast(byte) 'T'];
	static const byte[] TYPE_IEND = [cast(byte) 'I', cast(byte) 'E', cast(byte) 'N', cast(byte) 'D'];
	static const byte[] TYPE_tRNS = [cast(byte) 't', cast(byte) 'R', cast(byte) 'N', cast(byte) 'S'];
	
	static int[] CRC_TABLE;
	private static boolean staticCtor_done = false;
	static void staticCtor() {
		if(staticCtor_done) return;
		staticCtor_done = true;
	
		CRC_TABLE = new int[256];
		for (int i = 0; i < 256; i++) {
			CRC_TABLE[i] = i;
			for (int j = 0; j < 8; j++) {
				if ((CRC_TABLE[i] & 0x1) == 0) {
					CRC_TABLE[i] = (CRC_TABLE[i] >> 1) & 0x7FFFFFFF;
				} else {
					CRC_TABLE[i] = 0xEDB88320 ^ ((CRC_TABLE[i] >> 1) & 0x7FFFFFFF);
				}
			}
		}	
	}
	
/**
 * Construct a PngChunk using the reference bytes
 * given.
 */	
this(byte[] reference) {
//	super();
	staticCtor();
	setReference(reference);
}

/**
 * Get the PngChunk's reference byteArray;
 */	
byte[] getReference() {
	return reference;
}

/**
 * Set the PngChunk's reference byteArray;
 */	
void setReference(byte[] reference) {
	this.reference = reference;
}

/**
 * Get the 32-bit integer from the reference ubyte
 * array at the given offset.
 */	
int getInt32(int offset) {
	int answer = 0;
	answer |= (reference[offset] & 0xFF) << 24;
	answer |= (reference[offset + 1] & 0xFF) << 16;
	answer |= (reference[offset + 2] & 0xFF) << 8;
	answer |= (reference[offset + 3] & 0xFF);
	return answer;	
}

/**
 * Set the 32-bit integer in the reference ubyte
 * array at the given offset.
 */	
void setInt32(int offset, int value) {
	reference[offset] = cast(byte) ((value >> 24) & 0xFF);
	reference[offset + 1] = cast(byte) ((value >> 16) & 0xFF);
	reference[offset + 2] = cast(byte) ((value >> 8) & 0xFF);
	reference[offset + 3] = cast(byte) (value & 0xFF);
}

/**
 * Get the length of the data component of this chunk.
 * This is not the length of the entire chunk.
 */	
int getLength() {
	return getInt32(LENGTH_OFFSET);
}

/**
 * Set the length of the data component of this chunk.
 * This is not the length of the entire chunk.
 */	
void setLength(int value) {
	setInt32(LENGTH_OFFSET, value);
}

/**
 * Get the chunk type. This is a four ubyte value.
 * Each ubyte should be an ASCII character.
 * The first ubyte is upper case if the chunk is critical.
 * The second ubyte is upper case if the chunk is publicly defined.
 * The third ubyte must be upper case.
 * The fourth ubyte is upper case if the chunk is unsafe to copy. 
 * Public chunk types are defined by the PNG Development Group.
 */	
byte[] getTypeBytes() {
	byte[] type = new byte[4];
	System.arraycopy(reference, TYPE_OFFSET, type, 0, TYPE_FIELD_LENGTH);
	return type;
}	

/**
 * Set the chunk type. This is a four ubyte value.
 * Each ubyte should be an ASCII character.
 * The first ubyte is upper case if the chunk is critical.
 * The second ubyte is upper case if the chunk is publicly defined.
 * The third ubyte must be upper case.
 * The fourth ubyte is upper case if the chunk is unsafe to copy. 
 * Public chunk types are defined by the PNG Development Group.
 */	
void setType(byte[] value) {
	if (value.length != TYPE_FIELD_LENGTH) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_ARGUMENT);
	}
	System.arraycopy(value, 0, reference, TYPE_OFFSET, TYPE_FIELD_LENGTH);
}

/**
 * Get the chunk's data.
 */
byte[] getData() {
	int dataLength = getLength();
	if (reference.length < MIN_LENGTH + dataLength) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_RANGE);
	}
	byte[] data = new byte[dataLength];
	System.arraycopy(reference, DATA_OFFSET, data, 0, dataLength);
	return data;
}

/**
 * Set the chunk's data.
 * This method has two side-effects.
 * 1. It will set the length field to be the length
 *    of the data array given.
 * 2. It will set the CRC field to the computed CRC
 *    value of the data array given.
 */
void setData(byte[] data) {
	setLength(data.length);
	System.arraycopy(data, 0, reference, DATA_OFFSET, data.length);
	setCRC(computeCRC());
}

/**
 * Get the CRC value for the chunk's data.
 * Ensure that the length field has a good
 * value before making this call.
 */
int getCRC() {
	int crcOffset = DATA_OFFSET + getLength();
	return getInt32(crcOffset);
}

/**
 * Set the CRC value for the chunk's data.
 * Ensure that the length field has a good
 * value before making this call.
 */
void setCRC(int value) {
	int crcOffset = DATA_OFFSET + getLength();
	setInt32(crcOffset, value);
}

/**
 * Get the chunk's total size including the length, type, and crc fields.
 */
int getSize() {
	return MIN_LENGTH + getLength();
}

/**
 * Compute the CRC value for the chunk's data. Answer
 * whether this value matches the value stored in the
 * chunk.
 */
boolean checkCRC() {
	int crc = computeCRC();
	int storedCRC = getCRC();
	return crc == storedCRC;
}

/**
 * Answer the CRC value of chunk's data.
 */
int computeCRC() {
	int crc = 0xFFFFFFFF;
	int start = TYPE_OFFSET;
	int stop = DATA_OFFSET + getLength();
	for (int i = start; i < stop; i++) {
		int index = (crc ^ reference[i]) & 0xFF;
		crc =  CRC_TABLE[index] ^ ((crc >> 8) & 0x00FFFFFF);
	}
	return ~crc;
}

boolean typeMatchesArray(byte[] array) {
	for (int i = 0; i < TYPE_FIELD_LENGTH; i++) {
		if (reference[TYPE_OFFSET + i] != array[i]){
			return false;
		}
	}	
	return true;
}

boolean isCritical() {
	return Character.isUpperCase(cast(wchar) getTypeBytes()[0]);
}

int getChunkType() {
	if (typeMatchesArray(TYPE_IHDR)) return CHUNK_IHDR;
	if (typeMatchesArray(TYPE_PLTE)) return CHUNK_PLTE;
	if (typeMatchesArray(TYPE_IDAT)) return CHUNK_IDAT;
	if (typeMatchesArray(TYPE_IEND)) return CHUNK_IEND;
	if (typeMatchesArray(TYPE_tRNS)) return CHUNK_tRNS;	
	return CHUNK_UNKNOWN;
}




/**
 * Read the next PNG chunk from the input stream given.
 * If unable to read a chunk, return null.
 */
static PngChunk readNextFromStream(LEDataInputStream stream) {
	staticCtor();
	
	try {
		int headerLength = LENGTH_FIELD_LENGTH + TYPE_FIELD_LENGTH;
		byte[] headerBytes = new byte[headerLength];
		int result = stream.readBytes(headerBytes, 0, headerLength);
		stream.unread(headerBytes);
		if (result != headerLength) return null;
		
		PngChunk tempChunk = new PngChunk(headerBytes);
		
		int chunkLength = tempChunk.getSize();
		byte[] chunk = new byte[chunkLength];
		result = stream.readBytes(chunk, 0, chunkLength);
		if (result != chunkLength) return null;
	
		switch (tempChunk.getChunkType()) {
			case CHUNK_IHDR:
				return new PngIhdrChunk(chunk);
			case CHUNK_PLTE:
				return new PngPlteChunk(chunk);
			case CHUNK_IDAT:
				return new PngIdatChunk(chunk);
			case CHUNK_IEND:
				return new PngIendChunk(chunk);
			case CHUNK_tRNS:
				return new PngTrnsChunk(chunk);
			default:
				return new PngChunk(chunk);
		}		
	} catch (Exception e) {
		return null;
	}
	return null;
}

/**
 * Answer whether the chunk is a valid PNG chunk.
 */
void validate(PngFileReadState readState, PngIhdrChunk headerChunk) {
	if (reference.length < MIN_LENGTH) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	
	byte[] type = getTypeBytes();
	
	// The third character MUST be upper case.
	if (!Character.isUpperCase(cast(wchar) type[2])) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	
	// All characters must be letters.
	for (int i = 0; i < TYPE_FIELD_LENGTH; i++) {
		if (!Compatibility.isLetter(cast(wchar) type[i])) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
	
	// The stored CRC must match the data's computed CRC.
	if (!checkCRC()) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
}

/**
 * Provided so that subclasses can override and add
 * data to the toString() call.
 */
void contributeToString(char[] buffer) {}

public char[] toString() {
	
	char[] buffer;
	buffer ~= ("{");
	buffer ~= ("\n\tLength: ");
	buffer ~= Int.toString(getLength());
	buffer ~= ("\n\tType: ");
	byte[] type = getTypeBytes();
	for(int i = 0; i < type.length; i++) {
		buffer ~= cast(char) type[i];
	}
	
	contributeToString(buffer);
	
	buffer ~= ("\n\tCRC: ");
	buffer ~= (Converter.toHex(getCRC()));
	buffer ~= ("\n}");
	return buffer;
}

}
// end of "class PngChunk"


public class PngChunkReader {
	LEDataInputStream inputStream;
	PngFileReadState readState;
	PngIhdrChunk headerChunk;
	PngPlteChunk paletteChunk;
	
this(LEDataInputStream inputStream) {
	this.inputStream = inputStream;
	readState = new PngFileReadState();
	headerChunk = null;
}

PngIhdrChunk getIhdrChunk() {
	if (headerChunk is null) {
		try { 
			PngChunk chunk = PngChunk.readNextFromStream(inputStream);
			headerChunk = cast(PngIhdrChunk)chunk;
			headerChunk.validate(readState, null);
		} catch (Exception e) {
			DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
		}
	}
	return headerChunk;
}

PngChunk readNextChunk() {
	if (headerChunk is null) return getIhdrChunk();
	
	PngChunk chunk = PngChunk.readNextFromStream(inputStream);
	switch (chunk.getChunkType()) {
		case PngChunk.CHUNK_tRNS:
			(cast(PngTrnsChunk) chunk).validate(readState, headerChunk, paletteChunk);
			break;
		case PngChunk.CHUNK_PLTE:
			chunk.validate(readState, headerChunk);
			paletteChunk = cast(PngPlteChunk)chunk;
			break;
		default:
			chunk.validate(readState, headerChunk);
	}
	if (readState.readIDAT && !(chunk.getChunkType() == PngChunk.CHUNK_IDAT)) {
		readState.readPixelData = true;
	}
	return chunk;
}

boolean readPixelData() {
	return readState.readPixelData;
}

boolean hasMoreChunks() {
	return !readState.readIEND;
}

}
// end of "class PNGChunkReader"




public class PngDecodingDataStream {
	PngIdatChunk currentChunk;
	PngChunkReader chunkReader;
	byte currentByte;
	int nextByteIndex;
	int nextBitIndex;
	
	PngLzBlockReader lzBlockReader;
	int adlerValue;
	
	static int PRIME = 65521;
	static int MAX_BIT = 7;		
	
this(PngIdatChunk idatChunk, PngChunkReader chunkReader) {
//	super();
	this.currentChunk = idatChunk;
	this.chunkReader = chunkReader;
	nextByteIndex = 0;
	nextBitIndex = MAX_BIT + 1;
	adlerValue = 1;
	lzBlockReader = new PngLzBlockReader(this);
	readCompressedDataHeader();
	lzBlockReader.readNextBlockHeader();
}

/**
 * This method should be called when the image decoder thinks
 * that all of the compressed image data has been read. This
 * method will ensure that the next data value is an end of 
 * block marker. If there are more blocks after this one,
 * the method will read them and ensure that they are empty.
 */
void assertImageDataAtEnd() {
	lzBlockReader.assertCompressedDataAtEnd();
}

int getNextIdatBits(int length) {
	int value = 0;
	for (int i = 0; i < length; i++) {
		value |= (getNextIdatBit() << i);
	}
	return value;
}

byte getNextIdatBit() {
	if (nextBitIndex > MAX_BIT) {
		currentByte = getNextIdatByte();
		nextBitIndex = 0;
	}	
	int mask = 1 << nextBitIndex;
	nextBitIndex++;
	return ((currentByte & mask) > 0) ? cast(byte) 1 : cast(byte) 0;	
}

private PngIdatChunk getNextChunk() {
	PngChunk chunk = chunkReader.readNextChunk();
	if (chunk is null) error();
	if (chunk.getChunkType() != PngChunk.CHUNK_IDAT) error(); 
	return cast(PngIdatChunk)chunk;
}

byte getNextIdatByte() {
	if (nextByteIndex > currentChunk.getLength() - 1) {
		currentChunk = getNextChunk();
		nextByteIndex = 0;
	}
	byte nextByte = currentChunk.getDataByteAtOffset(nextByteIndex);
	nextByteIndex++;
	nextBitIndex = MAX_BIT + 1;
	return nextByte;
}

private void updateAdler(byte value) {
	int low = adlerValue & 0xFFFF;
	int high = (adlerValue >> 16) & 0xFFFF;
	int valueInt = value & 0xFF;
	low = (low + valueInt) % PRIME;
	high = (low + high) % PRIME;
	adlerValue = (high << 16) | low;
	
}

byte getNextDecodedByte() {
	byte nextDecodedByte = lzBlockReader.getNextByte();
	updateAdler(nextDecodedByte);
	return nextDecodedByte;
}

void error() {
	DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
}

private void readCompressedDataHeader() {
	byte headerByte1 = getNextIdatByte();
	byte headerByte2 = getNextIdatByte();
	
	int number = ((headerByte1 & 0xFF) << 8) | (headerByte2 & 0xFF);
	if (number % 31 != 0) error();
	
	int compressionMethod = headerByte1 & 0x0F;
	if (compressionMethod != 8) error();
	
	int windowSizeHint = (headerByte1 & 0xF0) >> 4;
	if (windowSizeHint > 7) error();
	int windowSize = (1 << (windowSizeHint + 8));
	lzBlockReader.setWindowSize(windowSize);
	
	int dictionary = (headerByte2 & (1 << 5));
	if (dictionary != 0) error();
	
//	int compressionLevel = (headerByte2 & 0xC0) >> 6;
}

void checkAdler() {
	
	// <Shawn> unsigned and signed, different in D ???
	ubyte b1 = cast(ubyte)getNextIdatByte();
	ubyte b2 = cast(ubyte)getNextIdatByte();
	ubyte b3 = cast(ubyte)getNextIdatByte();
	ubyte b4 = cast(ubyte)getNextIdatByte();
	
	int storedAdler = b1 << 24 | b2 << 16 | b3 << 8 | b4;
	
	/*
	// the following code gives an error code
	int storedAdler = ((getNextIdatByte() & 0xFF) << 24)
		| ((getNextIdatByte() & 0xFF) << 16)
		| ((getNextIdatByte() & 0xFF) << 8)
		| (getNextIdatByte() & 0xFF);
		*/
	if (storedAdler != adlerValue) error();
}

}
// end of "class PngDecodingDataStream"






class PNGFileFormat : FileFormat {
	static int SIGNATURE_LENGTH = 8;
	PngDecodingDataStream decodingStream;
	PngIhdrChunk headerChunk;
	PngPlteChunk paletteChunk;
	PngTrnsChunk trnsChunk;
	ImageData imageData;
	byte[] data;
	byte[] alphaPalette;

/**
 * Skip over signature data. This has already been
 * verified in isPNGFile(). 
 */
void readSignature() { // throws IOException {
	byte[] signature = new byte[SIGNATURE_LENGTH];
	inputStream.read(signature);
}
/**
 * Load the PNG image from the byte stream.
 */
ImageData[] loadFromByteStream() {
	try {
		readSignature();
		PngChunkReader chunkReader = new PngChunkReader(inputStream);
		headerChunk = chunkReader.getIhdrChunk();
		int imageSize = getAlignedBytesPerRow() * headerChunk.getHeight();
		data = new byte[imageSize];		
		imageData = ImageData.internal_new(
			headerChunk.getWidth(),
			headerChunk.getHeight(),
			headerChunk.getDwtBitsPerPixel(),
			new PaletteData(0, 0, 0),
			4,
			data,
			0,
			null,
			null,
			-1,
			-1,
			DWT.IMAGE_PNG,
			0,
			0,
			0,
			0);		
			
		if (headerChunk.usesDirectColor()) {
			imageData.palette = headerChunk.getPaletteData();
		}
		
		// Read and process chunks until the IEND chunk is encountered.
		while (chunkReader.hasMoreChunks()) {
			readNextChunk(chunkReader);
		}
						
		ImageData[] ImageDataArr;
		ImageDataArr ~= imageData;
		return ImageDataArr;
	} catch (StreamException e) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
		return null;
	}
}
/**
 * Read and handle the next chunk of data from the 
 * PNG file.
 */
void readNextChunk(PngChunkReader chunkReader) {
	PngChunk chunk = chunkReader.readNextChunk();
	switch (chunk.getChunkType()) {
		case PngChunk.CHUNK_IEND:
			break;
		case PngChunk.CHUNK_PLTE:
			if (!headerChunk.usesDirectColor()) {
				paletteChunk = cast(PngPlteChunk)chunk;
				imageData.palette = paletteChunk.getPaletteData();						
			}			
			break;
		case PngChunk.CHUNK_tRNS:
			PngTrnsChunk trnsChunk = cast(PngTrnsChunk)chunk;
			if (trnsChunk.getTransparencyType(headerChunk) == 
				PngTrnsChunk.TRANSPARENCY_TYPE_PIXEL) 
			{
				imageData.transparentPixel = 
					trnsChunk.getDwtTransparentPixel(headerChunk);
			} else {
				alphaPalette = trnsChunk.getAlphaValues(headerChunk, paletteChunk);
				int transparentCount = 0, transparentPixel = -1;
				for (int i = 0; i < alphaPalette.length; i++) {
					if ((alphaPalette[i] & 0xFF) != 255) {
						transparentCount++;
						transparentPixel = i;
					}
				}
				if (transparentCount == 0) {
					alphaPalette = null;
				} else if (transparentCount == 1 && alphaPalette[transparentPixel] == 0) {
					alphaPalette = null;
					imageData.transparentPixel = transparentPixel;
				}
			}
			break;
		case PngChunk.CHUNK_IDAT:
			if (chunkReader.readPixelData()) {
				// All IDAT chunks in an image file must be
				// sequential. If the pixel data has already
				// been read and another IDAT block is encountered,
				// then this is an invalid image.
				DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
			} else {
				// Read in the pixel data for the image. This should
				// go through all the image's IDAT chunks. 	
				PngIdatChunk dataChunk = cast(PngIdatChunk)chunk;
				readPixelData(dataChunk, chunkReader);				
			}
			break;
		default:
			if (chunk.isCritical()) {
				// All critical chunks must be supported.
				DWT.error(__FILE__, __LINE__, DWT.ERROR_NOT_IMPLEMENTED);
			}
	}
}
void unloadIntoByteStream(ImageData p1) {
	DWT.error(__FILE__, __LINE__, DWT.ERROR_NOT_IMPLEMENTED);
}
boolean isFileFormat(LEDataInputStream stream) {
	try {
		byte[] signature = new byte[SIGNATURE_LENGTH];
		stream.read(signature);
		stream.unread(signature);
		if ((signature[0] & 0xFF) != 137) return false; //137
		if ((signature[1] & 0xFF) != 80) return false; //P
		if ((signature[2] & 0xFF) != 78) return false; //N
		if ((signature[3] & 0xFF) != 71) return false; //G
		if ((signature[4] & 0xFF) != 13) return false; //<RETURN>
		if ((signature[5] & 0xFF) != 10) return false; //<LINEFEED>
		if ((signature[6] & 0xFF) != 26) return false; //<CTRL/Z>
		if ((signature[7] & 0xFF) != 10) return false; //<LINEFEED>		
		return true;
	} catch (Exception e) {
		return false;
	}
}
/**
 * DWT does not support 16-bit depths. If this image uses
 * 16-bit depths, convert the data to an 8-bit depth.
 */
byte[] validateBitDepth(byte[] data) {
	if (headerChunk.getBitDepth() > 8) {
		byte[] result = new byte[data.length / 2];
		compress16BitDepthTo8BitDepth(data, 0, result, 0, result.length);
		return result;
	} else {
		return data;
	}
}
/**
 * DWT does not support greyscale as a color type. For
 * plain grayscale, we create a palette. For Grayscale
 * with Alpha, however, we need to convert the pixels
 * to use RGB values.
 * Note: This method assumes that the bit depth of the
 * data has already been restricted to 8 or less.
 */
void setPixelData(byte[] data, ImageData imageData) {
	switch (headerChunk.getColorType()) {
		case PngIhdrChunk.COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
		{
			int width = imageData.width;
			int height = imageData.height;
			int destBytesPerLine = imageData.bytesPerLine;
			/*
			* If the image uses 16-bit depth, it is converted
			* to an 8-bit depth image.
			*/
			int srcBytesPerLine = getAlignedBytesPerRow();
			if (headerChunk.getBitDepth() > 8) srcBytesPerLine /= 2;

			byte[] rgbData = new byte[destBytesPerLine * height];
			byte[] alphaData = new byte[width * height];
			for (int y = 0; y < height; y++) {
				int srcIndex = srcBytesPerLine * y;
				int destIndex = destBytesPerLine * y;
				int destAlphaIndex = width * y;
				for (int x = 0; x < width; x++) {
					byte grey = data[srcIndex];
					byte alpha = data[srcIndex + 1];
					rgbData[destIndex + 0] = grey;
					rgbData[destIndex + 1] = grey;
					rgbData[destIndex + 2] = grey;
					alphaData[destAlphaIndex] = alpha;
					srcIndex += 2;
					destIndex += 3;
					destAlphaIndex++;
				}
			}
			imageData.data = rgbData;
			imageData.alphaData = alphaData;
			break;
		}
		case PngIhdrChunk.COLOR_TYPE_RGB_WITH_ALPHA:
		{
			int width = imageData.width;
			int height = imageData.height;
			int destBytesPerLine = imageData.bytesPerLine;
			int srcBytesPerLine = getAlignedBytesPerRow();
			/*
			* If the image uses 16-bit depth, it is converted
			* to an 8-bit depth image.
			*/
			if (headerChunk.getBitDepth() > 8) srcBytesPerLine /= 2;

			byte[] rgbData = new byte[destBytesPerLine * height];
			byte[] alphaData = new byte[width * height];
			for (int y = 0; y < height; y++) {
				int srcIndex = srcBytesPerLine * y;
				int destIndex = destBytesPerLine * y;
				int destAlphaIndex = width * y;
				for (int x = 0; x < width; x++) {
					rgbData[destIndex + 0] = data[srcIndex + 0];
					rgbData[destIndex + 1] = data[srcIndex + 1];
					rgbData[destIndex + 2] = data[srcIndex + 2];
					alphaData[destAlphaIndex] = data[srcIndex + 3];
					srcIndex += 4;
					destIndex += 3;
					destAlphaIndex++;
				}
			}
			imageData.data = rgbData;
			imageData.alphaData = alphaData;
			break;
		}		
		case PngIhdrChunk.COLOR_TYPE_RGB:
			imageData.data = data;
			break;
		case PngIhdrChunk.COLOR_TYPE_PALETTE:
			imageData.data = data;
			if (alphaPalette !is null) {
				int size = imageData.width * imageData.height;
				byte[] alphaData = new byte[size];
				byte[] pixelData = new byte[size];
				imageData.getPixels(0, 0, size, pixelData, 0);
				for (int i = 0; i < pixelData.length; i++) {
					alphaData[i] = alphaPalette[pixelData[i] & 0xFF];
				}
				imageData.alphaData = alphaData;
			}
			break;
		default:
			imageData.data = data;
			break;
	}
}
/**
 * PNG supports some color types and bit depths that are 
 * unsupported by DWT. If the image uses an unsupported
 * color type (either of the gray scale types) or bit
 * depth (16), convert the data to an DWT-supported
 * format. Then assign the data into the ImageData given.
 */
void setImageDataValues(byte[] data, ImageData imageData) {
	byte[] result = validateBitDepth(data);
	setPixelData(result, imageData);
}
/**
 * Read the image data from the data stream. This must handle
 * decoding the data, filtering, and interlacing.
 */
void readPixelData(PngIdatChunk chunk, PngChunkReader chunkReader) {
	decodingStream = new PngDecodingDataStream(chunk, chunkReader);
	int interlaceMethod = headerChunk.getInterlaceMethod();
	if (interlaceMethod == PngIhdrChunk.INTERLACE_METHOD_NONE) {
		readNonInterlacedImage();
	} else {
		readInterlacedImage();
	}
	decodingStream.assertImageDataAtEnd();
	decodingStream.checkAdler();
}
/**
 * Answer the number of bytes in a word-aligned row of pixel data.
 */
int getAlignedBytesPerRow() {
	return ((getBytesPerRow(headerChunk.getWidth()) + 3) / 4) * 4;
}
/**
 * Answer the number of bytes in each row of the image
 * data. Each PNG row is byte-aligned, so images with bit
 * depths less than a byte may have unused bits at the
 * end of each row. The value of these bits is undefined.
 */
int getBytesPerRow() {
	return getBytesPerRow(headerChunk.getWidth());
}
/**
 * Answer the number of bytes needed to represent a pixel.
 * This value depends on the image's color type and bit
 * depth. 
 * Note that this method rounds up if an image's pixel size
 * isn't byte-aligned.
 */
int getBytesPerPixel() {
	int bitsPerPixel = headerChunk.getBitsPerPixel();
	return (bitsPerPixel + 7) / 8;	
}
/**
 * Answer the number of bytes in a row of the given pixel
 * width. Each row is byte-aligned, so images with bit
 * depths less than a byte may have unused bits at the
 * end of each row. The value of these bits is undefined.
 */
int getBytesPerRow(int rowWidthInPixels) {
	int bitsPerPixel = headerChunk.getBitsPerPixel();
	int bitsPerRow = bitsPerPixel * rowWidthInPixels;
	int bitsPerByte = 8;
	return (bitsPerRow + (bitsPerByte - 1)) / bitsPerByte;
}
/**
 * 1. Read one of the seven frames of interlaced data.
 * 2. Update the imageData.
 * 3. Notify the image loader's listeners of the frame load.
 */
void readInterlaceFrame(
	int rowInterval,
	int columnInterval,
	int startRow,
	int startColumn,
	int frameCount) 
{
	int width = headerChunk.getWidth();
	int align_renamededBytesPerRow = getAlignedBytesPerRow();
	int height = headerChunk.getHeight();
	if (startRow >= height || startColumn >= width) return;
	
	int pixelsPerRow = (width - startColumn + columnInterval - 1) / columnInterval;
	int bytesPerRow = getBytesPerRow(pixelsPerRow);
	byte[] row1 = new byte[bytesPerRow];
	byte[] row2 = new byte[bytesPerRow];
	byte[] currentRow = row1;	
	byte[] lastRow = row2;	
	for (int row = startRow; row < height; row += rowInterval) {
		byte filterType = decodingStream.getNextDecodedByte();
		for (int col = 0; col < bytesPerRow; col++) {
			currentRow[col] = decodingStream.getNextDecodedByte();
		}
		filterRow(currentRow, lastRow, filterType);
		if (headerChunk.getBitDepth() >= 8) {
			int bytesPerPixel = getBytesPerPixel();
			int dataOffset = (row * align_renamededBytesPerRow) + (startColumn * bytesPerPixel);
			for (int rowOffset = 0; rowOffset < currentRow.length; rowOffset += bytesPerPixel) {
				for (int byteOffset = 0; byteOffset < bytesPerPixel; byteOffset++) {
					data[dataOffset + byteOffset] = currentRow[rowOffset + byteOffset];
				}
				dataOffset += (columnInterval * bytesPerPixel);
			}
		} else {
			int bitsPerPixel = headerChunk.getBitDepth();
			int pixelsPerByte = 8 / bitsPerPixel;
			int column = startColumn;
			int rowBase = row * align_renamededBytesPerRow;
			int valueMask = 0;
			for (int i = 0; i < bitsPerPixel; i++) {
				valueMask <<= 1;
				valueMask |= 1;
			}
			int maxShift = 8 - bitsPerPixel;
			for (int byteOffset = 0; byteOffset < currentRow.length; byteOffset++) {
				for (int bitOffset = maxShift; bitOffset >= 0; bitOffset -= bitsPerPixel) {
					if (column < width) {
						int dataOffset = rowBase + (column * bitsPerPixel / 8);							
						int value = (currentRow[byteOffset] >> bitOffset) & valueMask;
						int dataShift = maxShift - (bitsPerPixel * (column % pixelsPerByte));
						data[dataOffset] |= value << dataShift;
					}
					column += columnInterval;
				}
			}
		}
		currentRow = (currentRow is row1) ? row2 : row1;
		lastRow = (lastRow is row1) ? row2 : row1;
	}
	setImageDataValues(data, imageData);
	fireInterlacedFrameEvent(frameCount);
}
/**
 * Read the pixel data for an interlaced image from the
 * data stream.
 */
void readInterlacedImage() {
	readInterlaceFrame(8, 8, 0, 0, 0);
	readInterlaceFrame(8, 8, 0, 4, 1);	
	readInterlaceFrame(8, 4, 4, 0, 2);	
	readInterlaceFrame(4, 4, 0, 2, 3);
	readInterlaceFrame(4, 2, 2, 0, 4);
	readInterlaceFrame(2, 2, 0, 1, 5);	
	readInterlaceFrame(2, 1, 1, 0, 6);
}
/**
 * Fire an event to let listeners know that an interlaced
 * frame has been loaded.
 * finalFrame should be true if the image has finished
 * loading, false if there are more frames to come.
 */
void fireInterlacedFrameEvent(int frameCount) {
	if (loader.hasListeners()) {
		ImageData image = cast(ImageData)imageData.clone();
		boolean finalFrame = frameCount == 6;
		loader.notifyListeners(new ImageLoaderEvent(loader, image, frameCount, finalFrame));
	}
}
/**
 * Read the pixel data for a non-interlaced image from the
 * data stream.
 * Update the imageData to reflect the new data.
 */
void readNonInterlacedImage() {
	int dataOffset = 0;
	int align_renamededBytesPerRow = getAlignedBytesPerRow();
	int bytesPerRow = getBytesPerRow();
	byte[] row1 = new byte[bytesPerRow];
	byte[] row2 = new byte[bytesPerRow];
	byte[] currentRow = row1;	
	byte[] lastRow = row2;
	for (int row = 0; row < headerChunk.getHeight(); row++) {
		byte filterType = decodingStream.getNextDecodedByte();
		for (int col = 0; col < bytesPerRow; col++) {
			currentRow[col] = decodingStream.getNextDecodedByte();
		}
		filterRow(currentRow, lastRow, filterType);
		System.arraycopy(currentRow, 0, data, dataOffset, bytesPerRow);
		dataOffset += align_renamededBytesPerRow;
		currentRow = (currentRow is row1) ? row2 : row1;
		lastRow = (lastRow is row1) ? row2 : row1;
	}
	setImageDataValues(data, imageData);
}
/**
 * DWT does not support 16-bit depth color formats.
 * Convert the 16-bit data to 8-bit data.
 * The correct way to do this is to multiply each
 * 16 bit value by the value:
 * (2^8 - 1) / (2^16 - 1).
 * The fast way to do this is just to drop the low
 * byte of the 16-bit value.
 */
static void compress16BitDepthTo8BitDepth(
	byte[] source,
	int sourceOffset,
	byte[] destination, 
	int destinationOffset,
	int numberOfValues) 
{
	//double multiplier = (Compatibility.pow2(8) - 1) / (Compatibility.pow2(16) - 1);
	for (int i = 0; i < numberOfValues; i++) {
		int sourceIndex = sourceOffset + (2 * i);
		int destinationIndex = destinationOffset + i;
		//int value = (source[sourceIndex] << 8) | source[sourceIndex + 1];
		//byte compressedValue = (byte)(value * multiplier);
		byte compressedValue = source[sourceIndex];
		destination[destinationIndex] = compressedValue;
	}
}
/**
 * DWT does not support 16-bit depth color formats.
 * Convert the 16-bit data to 8-bit data.
 * The correct way to do this is to multiply each
 * 16 bit value by the value:
 * (2^8 - 1) / (2^16 - 1).
 * The fast way to do this is just to drop the low
 * byte of the 16-bit value.
 */
static int compress16BitDepthTo8BitDepth(int value) {
	//double multiplier = (Compatibility.pow2(8) - 1) / (Compatibility.pow2(16) - 1);
	//byte compressedValue = (byte)(value * multiplier);
	return value >> 8;
}
/**
 * PNG supports four filtering types. These types are applied
 * per row of image data. This method unfilters the given row
 * based on the filterType.
 */
void filterRow(byte[] row, byte[] previousRow, int filterType) {
	int byteOffset = headerChunk.getFilterByteOffset();
	switch (filterType) {
		case PngIhdrChunk.FILTER_NONE:
			break;
		case PngIhdrChunk.FILTER_SUB:
			for (int i = byteOffset; i < row.length; i++) {
				int current = row[i] & 0xFF;
				int left = row[i - byteOffset] & 0xFF;
				row[i] = cast(byte)((current + left) & 0xFF);
			}
			break;
		case PngIhdrChunk.FILTER_UP:
			for (int i = 0; i < row.length; i++) {
				int current = row[i] & 0xFF;
				int above = previousRow[i] & 0xFF;				
				row[i] = cast(byte)((current + above) & 0xFF);
			}
			break;
		case PngIhdrChunk.FILTER_AVERAGE:
			for (int i = 0; i < row.length; i++) {
				int left = (i < byteOffset) ? 0 : row[i - byteOffset] & 0xFF;
				int above = previousRow[i] & 0xFF;
				int current = row[i] & 0xFF;
				row[i] = cast(byte)((current + ((left + above) / 2)) & 0xFF);
			}
			break;
		case PngIhdrChunk.FILTER_PAETH:
			for (int i = 0; i < row.length; i++) {
				int left = (i < byteOffset) ? 0 : row[i - byteOffset] & 0xFF;
				int aboveLeft = (i < byteOffset) ? 0 : previousRow[i - byteOffset] & 0xFF;
				int above = previousRow[i] & 0xFF;
				
				int a = Math.abs(above - aboveLeft);
				int b = Math.abs(left - aboveLeft);
				int c = Math.abs(left - aboveLeft + above - aboveLeft);
				
				int preductor = 0;
				if (a <= b && a <= c) {
					preductor = left;
				} else if (b <= c) {
					preductor = above;
				} else {
					preductor = aboveLeft;
				}
				
				int currentValue = row[i] & 0xFF;
				row[i] = cast(byte) ((currentValue + preductor) & 0xFF);
			}
			break;
		default : break;
	}
}

}

// end of "class PNGFileFormat"


class PngFileReadState {
	boolean readIHDR;
	boolean readPLTE;
	boolean readIDAT;
	boolean readIEND;
	
	// Non - critical chunks
	boolean readBKGD;
	boolean readTRNS;
	
	// Set to true after IDATs have been read.
	boolean readPixelData;
}

// end of "class PngFileReadState"


public class PngHuffmanTable {
	CodeLengthInfo[] codeLengthInfo;
	int[] codeValues;
	
	static int MAX_CODE_LENGTH = 15;
	static int BAD_CODE = 0xFFFFFFF;

this (int[] lengths) {
//	super();
	initialize(lengths);
	generateTable(lengths);
}

private void initialize(int[] lengths) {
	codeValues = new int[lengths.length];
	for (int i = 0; i < codeValues.length; i++) {
		codeValues[i] = i;
	}
	
	// minCodesByLength[n] : The smallest Huffman code of length n + 1.
	// maxCodesByLength[n] : The largest Huffman code of length n + 1.
	// indexesByLength[n] : Index into the values array. First value with a code of length n + 1.
	codeLengthInfo = new CodeLengthInfo[MAX_CODE_LENGTH];
	for (int i = 0; i < MAX_CODE_LENGTH; i++) {
		codeLengthInfo[i] = new CodeLengthInfo();
		codeLengthInfo[i].length = i;
		codeLengthInfo[i].baseIndex = 0;
		codeLengthInfo[i].min = BAD_CODE;
		codeLengthInfo[i].max = -1;
	}
}
	
private void generateTable(int[] lengths) {
	// Sort the values. Primary key is code size. Secondary key is value.
	for (int i = 0; i < lengths.length - 1; i++) {
		for (int j = i + 1; j < lengths.length; j++) {
			if (lengths[j] < lengths[i]
				|| (lengths[j] == lengths[i]
				&& codeValues[j] < codeValues[i]))
			{
				int tmp;
				tmp = lengths[j];
				lengths[j] = lengths[i];
				lengths[i] = tmp;
				tmp = codeValues[j];
				codeValues[j] = codeValues[i];
				codeValues[i] = tmp;
			}
		}
	}

	// These values in these arrays correspond to the elements of the
	// "values" array. The Huffman code for codeValues[N] is codes[N]
	// and the length of the code is lengths[N].
	int[] codes = new int[lengths.length];
	int lastLength = 0;
	int code = 0;
	for (int i = 0; i < lengths.length; i++) {
		while (lastLength != lengths[i]) {
			lastLength++;
			code <<= 1;
		}
		if (lastLength != 0) {
			codes[i] = code;
			code++;
		}
	}
	
	int last = 0;
	for (int i = 0; i < lengths.length; i++) {
		if (last != lengths[i]) {
			last = lengths[i];
			codeLengthInfo[last - 1].baseIndex = i;
			codeLengthInfo[last - 1].min = codes[i];
		}
		if (last != 0) codeLengthInfo[last - 1].max = codes[i];
	}
}

int getNextValue(PngDecodingDataStream stream) {
	int code = stream.getNextIdatBit();
	int codelength = 0;

	// Here we are taking advantage of the fact that 1 bits are used as
	// a prefix to the longer codeValues.
	while (codelength < MAX_CODE_LENGTH && code > codeLengthInfo[codelength].max) {
		code = ((code << 1) | stream.getNextIdatBit());
        codelength++;
	}
	if (codelength >= MAX_CODE_LENGTH) stream.error();

	// Now we have a Huffman code of length (codelength + 1) that
	// is somewhere in the range
	// minCodesByLength[codelength]..maxCodesByLength[codelength].
	// This code is the (offset + 1)'th code of (codelength + 1);
	int offset = code - codeLengthInfo[codelength].min;

	// indexesByLength[codelength] is the first code of length (codelength + 1)
	// so now we can look up the value for the Huffman code in the table.
	int index = codeLengthInfo[codelength].baseIndex + offset;
	return codeValues[index];
}	
	
class CodeValuePair {
	int value;
	int code;
}
class CodeLengthInfo {
	int length;
	int max;
	int min;
	int baseIndex;
}

}
// end of "class PngHuffmanTable"

public class PngHuffmanTables {
	PngHuffmanTable literalTable;
	PngHuffmanTable distanceTable;
	
	static PngHuffmanTable FixedLiteralTable;
	static PngHuffmanTable FixedDistanceTable;
	
	static int LiteralTableSize = 288;
	static int[] FixedLiteralLengths = [
        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
        9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
        9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
        9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
        9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
        9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7,
        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8,
	];

	static int DistanceTableSize = 32;
	static int[] FixedDistanceLengths = [
		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
		5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
	];
	
	static int LengthCodeTableSize = 19;
	static int[] LengthCodeOrder = [
		16, 17, 18, 0, 8, 7, 9, 6, 10, 5,
		11, 4, 12, 3, 13, 2, 14, 1, 15
	];
	
static PngHuffmanTables getDynamicTables(PngDecodingDataStream stream){
	return new PngHuffmanTables(stream);
}
static PngHuffmanTables getFixedTables() {
	return new PngHuffmanTables();
}

private PngHuffmanTable getFixedLiteralTable() {
	if (FixedLiteralTable is null) {
		FixedLiteralTable = new PngHuffmanTable(FixedLiteralLengths);
	}
	return FixedLiteralTable;
}

private PngHuffmanTable getFixedDistanceTable() {
	if (FixedDistanceTable is null) {
		FixedDistanceTable = new PngHuffmanTable(FixedDistanceLengths);
	}
	return FixedDistanceTable;
}

private this () {
	literalTable = getFixedLiteralTable();
	distanceTable = getFixedDistanceTable();
}

private this (PngDecodingDataStream stream) {
	int literals = PngLzBlockReader.FIRST_LENGTH_CODE 
		+ stream.getNextIdatBits(5);
	int distances = PngLzBlockReader.FIRST_DISTANCE_CODE 
		+ stream.getNextIdatBits(5);
	int codeLengthCodes = PngLzBlockReader.FIRST_CODE_LENGTH_CODE 
		+ stream.getNextIdatBits(4);

	if (codeLengthCodes > PngLzBlockReader.LAST_CODE_LENGTH_CODE) {
		stream.error();
	}
	
	/* Tricky, tricky, tricky. The length codes are stored in
	 * a very odd order. (For the order, see the definition of
	 * the static field lengthCodeOrder.) Also, the data may 
	 * not contain values for all the codes. It may just contain 
	 * values for the first X number of codes. The table should
	 * be of size <LengthCodeTableSize> regardless of the number
	 * of values actually given in the table.
	 */	
	int[] lengthCodes = new int[LengthCodeTableSize];
	for (int i = 0; i < codeLengthCodes; i++) {
		lengthCodes[LengthCodeOrder[i]] = stream.getNextIdatBits(3);
	}
	PngHuffmanTable codeLengthsTable = new PngHuffmanTable(lengthCodes);
	
	int[] literalLengths = readLengths(
		stream, literals, codeLengthsTable, LiteralTableSize);
	int[] distanceLengths = readLengths(
		stream, distances, codeLengthsTable, DistanceTableSize);
	
	literalTable = new PngHuffmanTable(literalLengths);
	distanceTable = new PngHuffmanTable(distanceLengths);
}

private int [] readLengths (PngDecodingDataStream stream, 
	int numLengths, 
	PngHuffmanTable lengthsTable,
	int tableSize) 
{
	int[] lengths = new int[tableSize];
	
	for (int index = 0; index < numLengths;) {
		int value = lengthsTable.getNextValue(stream);
		if (value < 16) {
			// Literal value
			lengths[index] = value;
			index++;
		} else if (value == 16) {
			// Repeat the previous code 3-6 times.
			int count = stream.getNextIdatBits(2) + 3;
			for (int i = 0; i < count; i++) {
				lengths[index] = lengths [index - 1];
				index++;
			}
		} else if (value == 17) {
			// Repeat 0 3-10 times.
			int count = stream.getNextIdatBits(3) + 3;
			for (int i = 0; i < count; i++) {
				lengths[index] = 0;
				index++;
			}
		} else if (value == 18) {
			// Repeat 0 11-138 times.
			int count = stream.getNextIdatBits(7) + 11;
			for (int i = 0; i < count; i++) {
				lengths[index] = 0;
				index++;
			}
		} else {
			stream.error();
		}
	}
	return lengths;
}

int getNextLiteralValue(PngDecodingDataStream stream) {
	return literalTable.getNextValue(stream);
}

int getNextDistanceValue(PngDecodingDataStream stream) {
	return distanceTable.getNextValue(stream);
}

}

// end of "PngHuffmantables"


class PngIdatChunk : PngChunk {

this(byte[] reference){
	super(reference);
}

/**
 * Answer whether the chunk is a valid IDAT chunk.
 */
void validate(PngFileReadState readState, PngIhdrChunk headerChunk) {
	if (!readState.readIHDR
		|| (headerChunk.getMustHavePalette() && !readState.readPLTE)
		|| readState.readIEND) 
	{
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	} else {
		readState.readIDAT = true;
	}
	
	super.validate(readState, headerChunk);
}

byte getDataByteAtOffset(int offset) {
	return reference[DATA_OFFSET + offset];
}

}
// end of "class PngIdatChunk"



class PngIendChunk : PngChunk {

this(byte[] reference){
	super(reference);
}

/**
 * Answer whether the chunk is a valid IEND chunk.
 */
void validate(PngFileReadState readState, PngIhdrChunk headerChunk) {
	// An IEND chunk is invalid if no IHDR has been read.
	// Or if a palette is required and has not been read.
	// Or if no IDAT chunk has been read.
	if (!readState.readIHDR
		|| (headerChunk.getMustHavePalette() && !readState.readPLTE)
		|| !readState.readIDAT
		|| readState.readIEND) 
	{
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	} else {
		readState.readIEND = true;
	}
	
	super.validate(readState, headerChunk);
	
	// IEND chunks are not allowed to have any data.
	if (getLength() > 0) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
}

}



class PngIhdrChunk : PngChunk {
	static const int EXPECTED_DATA_LENGTH = 13;
	
	static const int WIDTH_DATA_OFFSET = DATA_OFFSET + 0;
	static const int HEIGHT_DATA_OFFSET = DATA_OFFSET + 4;
	static const int BIT_DEPTH_OFFSET = DATA_OFFSET + 8;
	static const int COLOR_TYPE_OFFSET = DATA_OFFSET + 9;
	static const int COMPRESSION_METHOD_OFFSET = DATA_OFFSET + 10;
	static const int FILTER_METHOD_OFFSET = DATA_OFFSET + 11;
	static const int INTERLACE_METHOD_OFFSET = DATA_OFFSET + 12;
	
	static const int COLOR_TYPE_GRAYSCALE = 0;
	static const int COLOR_TYPE_RGB = 2;
	static const int COLOR_TYPE_PALETTE = 3;
	static const int COLOR_TYPE_GRAYSCALE_WITH_ALPHA = 4;
	static const int COLOR_TYPE_RGB_WITH_ALPHA = 6;
	
	static const int INTERLACE_METHOD_NONE = 0;
	static const int INTERLACE_METHOD_ADAM7 = 1;
	       
	static const int FILTER_NONE = 0;
	static const int FILTER_SUB = 1;
	static const int FILTER_UP = 2;
	static const int FILTER_AVERAGE = 3;
	static const int FILTER_PAETH = 4;
	        
	static const byte[] ValidBitDepths = [1, 2, 4, 8, 16];
	static const byte[] ValidColorTypes = [0, 2, 3, 4, 6];
	
/**
 * Construct a PNGChunk using the reference bytes
 * given.
 */	
this(byte[] reference) {
	super(reference);
}

/**
 * Get the image's width in pixels.
 */
int getWidth() {
	return getInt32(WIDTH_DATA_OFFSET);
}

/**
 * Set the image's width in pixels.
 */
void setWidth(int value) {
	setInt32(WIDTH_DATA_OFFSET, value);
}

/**
 * Get the image's height in pixels.
 */
int getHeight() {
	return getInt32(HEIGHT_DATA_OFFSET);
}

/**
 * Set the image's height in pixels.
 */
void setHeight(int value) {
	setInt32(HEIGHT_DATA_OFFSET, value);
}

/**
 * Get the image's bit depth.
 * This is limited to the values 1, 2, 4, 8, or 16.
 */
byte getBitDepth() {
	return reference[BIT_DEPTH_OFFSET];
}

/**
 * Set the image's bit depth.
 * This is limited to the values 1, 2, 4, 8, or 16.
 */
void setBitDepth(byte value) {
	reference[BIT_DEPTH_OFFSET] = value;
}

/**
 * Get the image's color type.
 * This is limited to the values:
 * 0 - Grayscale image.
 * 2 - RGB triple.
 * 3 - Palette.
 * 4 - Grayscale with Alpha channel.
 * 6 - RGB with Alpha channel.
 */
byte getColorType() {
	return reference[COLOR_TYPE_OFFSET];
}

/**
 * Set the image's color type.
 * This is limited to the values:
 * 0 - Grayscale image.
 * 2 - RGB triple.
 * 3 - Palette.
 * 4 - Grayscale with Alpha channel.
 * 6 - RGB with Alpha channel.
 */
void setColorType(byte value) {
	reference[COLOR_TYPE_OFFSET] = value;
}

/**
 * Get the image's compression method.
 * This value must be 0.
 */
byte getCompressionMethod() {
	return reference[COMPRESSION_METHOD_OFFSET];
}

/**
 * Set the image's compression method.
 * This value must be 0.
 */
void setCompressionMethod(byte value) {
	reference[COMPRESSION_METHOD_OFFSET] = value;
}

/**
 * Get the image's filter method.
 * This value must be 0.
 */
byte getFilterMethod() {
	return reference[FILTER_METHOD_OFFSET];
}

/**
 * Set the image's filter method.
 * This value must be 0.
 */
void setFilterMethod(byte value) {
	reference[FILTER_METHOD_OFFSET] = value;
}

/**
 * Get the image's interlace method.
 * This value is limited to:
 * 0 - No interlacing used.
 * 1 - Adam7 interlacing used.
 */
byte getInterlaceMethod() {
	return reference[INTERLACE_METHOD_OFFSET];
}

/**
 * Set the image's interlace method.
 * This value is limited to:
 * 0 - No interlacing used.
 * 1 - Adam7 interlacing used.
 */
void setInterlaceMethod(byte value) {
	reference[INTERLACE_METHOD_OFFSET] = value;
}

/**
 * Answer whether the chunk is a valid IHDR chunk.
 */
void validate(PngFileReadState readState, PngIhdrChunk headerChunk) {
	// An IHDR chunk is invalid if any other chunk has
	// been read.
	if (readState.readIHDR 
		|| readState.readPLTE
		|| readState.readIDAT
		|| readState.readIEND) 
	{
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	} else {
		readState.readIHDR = true;
	}
	
	super.validate(readState, headerChunk);
	
	if (getLength() != EXPECTED_DATA_LENGTH) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	if (getCompressionMethod() != 0) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	if (getInterlaceMethod() != INTERLACE_METHOD_NONE &&
		getInterlaceMethod() != INTERLACE_METHOD_ADAM7) {
			DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
	
	boolean colorTypeIsValid = false;
	byte colorType = getColorType();
	for (int i = 0; i < ValidColorTypes.length; i++) {
		if (ValidColorTypes[i] == colorType) {
			colorTypeIsValid = true;
			break;
		}
	}
	if (!colorTypeIsValid) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);

	boolean bitDepthIsValid = false;
	byte bitDepth = getBitDepth();
	for (int i = 0; i < ValidBitDepths.length; i++) {
		if (ValidBitDepths[i] == bitDepth) {
			bitDepthIsValid = true;
			break;
		}
	}
	if (!bitDepthIsValid) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	
	if ((colorType == COLOR_TYPE_RGB 
		|| colorType == COLOR_TYPE_RGB_WITH_ALPHA
		|| colorType == COLOR_TYPE_GRAYSCALE_WITH_ALPHA)
		&& bitDepth < 8) 
	{
			DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
	
	if (colorType == COLOR_TYPE_PALETTE && bitDepth > 8) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
}

char[] getColorTypeString() {
	switch (getColorType()) {
		case COLOR_TYPE_GRAYSCALE: 				return ("Grayscale");
		case COLOR_TYPE_RGB: 					return ("DWTRGB");		
		case COLOR_TYPE_PALETTE:				return ("Palette");
		case COLOR_TYPE_GRAYSCALE_WITH_ALPHA:	return ("Grayscale with Alpha");
		case COLOR_TYPE_RGB_WITH_ALPHA:			return ("DWTRGB with Alpha");
		default:								break;
	}
	return ("Unknown - ") ~ Int.toString(getColorType());
}

char[] getFilterMethodString() {
	switch (getFilterMethod()) {
		case FILTER_NONE:		return ("None");
		case FILTER_SUB:		return ("Sub");
		case FILTER_UP:			return ("Up");
		case FILTER_AVERAGE:	return ("Average");
		case FILTER_PAETH:		return ("Paeth");
		default:				break;
	}
	return ("Unknown");
}

char[] getInterlaceMethodString() {
	switch (getInterlaceMethod()) {
		case INTERLACE_METHOD_NONE: 	return ("Not Interlaced");
		case INTERLACE_METHOD_ADAM7:	return ("Interlaced - ADAM7");
		default:						return ("Unknown");
	}
	return ("Unknown");
}

void contributeToString(char[] buffer) {
	buffer ~= ("\n\tWidth: ");
	buffer ~= getWidth();
	buffer ~= ("\n\tHeight: ");
	buffer ~= getHeight();
	buffer ~= ("\n\tBit Depth: ");
	buffer ~= getBitDepth();
	buffer ~= ("\n\tColor Type: ");
	buffer ~= (getColorTypeString());
	buffer ~= ("\n\tCompression Method: ");
	buffer ~= getCompressionMethod();
	buffer ~= ("\n\tFilter Method: ");
	buffer ~= (getFilterMethodString());
	buffer ~= ("\n\tInterlace Method: ");
	buffer ~= (getInterlaceMethodString());
}

boolean getMustHavePalette() {
	return getColorType() == COLOR_TYPE_PALETTE;
}

boolean getCanHavePalette() {
	int colorType = getColorType();
	return colorType != COLOR_TYPE_GRAYSCALE && 
		colorType != COLOR_TYPE_GRAYSCALE_WITH_ALPHA;
}

/**
 * Answer the pixel size in bits based on the color type
 * and bit depth.
 */
int getBitsPerPixel() {
	int bitDepth = getBitDepth();
	switch (getColorType()) {
		case COLOR_TYPE_RGB_WITH_ALPHA:
			return 4 * bitDepth;
		case COLOR_TYPE_RGB:
			return 3 * bitDepth;
		case COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
			return 2 * bitDepth;
		case COLOR_TYPE_GRAYSCALE:
		case COLOR_TYPE_PALETTE:
			return bitDepth;
		default:
			DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
			return 0;
	}
	return 0;
}

/**
 * Answer the pixel size in bits based on the color type
 * and bit depth.
 */
int getDwtBitsPerPixel() {
	int bitDepth = getBitDepth();
	switch (getColorType()) {
		case COLOR_TYPE_RGB_WITH_ALPHA:
		case COLOR_TYPE_RGB:
		case COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
			return 24;
		case COLOR_TYPE_GRAYSCALE:
		case COLOR_TYPE_PALETTE:
			return Math.min(bitDepth, 8);
		default:
			DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
			return 0;
	}
	return 0;
}

int getFilterByteOffset() {
	if (getBitDepth() < 8) return 1;
	return getBitsPerPixel() / 8;
}

boolean usesDirectColor() {
	switch (getColorType()) {
		case COLOR_TYPE_GRAYSCALE:
		case COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
		case COLOR_TYPE_RGB:
		case COLOR_TYPE_RGB_WITH_ALPHA:
			return true;
		default:
			return false;
	}
	return false;
}

PaletteData createGrayscalePalette() {
	int bitDepth = Math.min(cast(int)getBitDepth(), 8);
	int max = Compatibility.pow2(bitDepth) - 1;
	int delta = 255 / max;
	int gray = 0;
	DWTRGB[] rgbs = new DWTRGB[max + 1]; 
	for (int i = 0; i <= max; i++) {
		rgbs[i] = new DWTRGB(gray, gray, gray);
		gray += delta;
	}
	return new PaletteData(rgbs);
}

PaletteData getPaletteData() {
	switch (getColorType()) {
		case COLOR_TYPE_GRAYSCALE:
			return createGrayscalePalette();
		case COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
		case COLOR_TYPE_RGB:
		case COLOR_TYPE_RGB_WITH_ALPHA:
			return new PaletteData(0xFF0000, 0xFF00, 0xFF);
		default:
			return null;
	}
			
	return null;

}


}




public class PngLzBlockReader {
	boolean readHeader;
	boolean isLastBlock;
	byte compressionType;
	int uncompressedBytesRemaining;
	PngDecodingDataStream stream;
	PngHuffmanTables huffmanTables;
	
	byte[] window;
	int windowIndex;
	int copyIndex;
	int copyBytesRemaining;
	
	static int UNCOMPRESSED = 0;
	static int COMPRESSED_FIXED = 1;
	static int COMPRESSED_DYNAMIC = 2;

	static int END_OF_COMPRESSED_BLOCK = 256;
	static int FIRST_LENGTH_CODE = 257;
	static int LAST_LENGTH_CODE = 285;
	static int FIRST_DISTANCE_CODE = 1;
	static int LAST_DISTANCE_CODE = 29;
	static int FIRST_CODE_LENGTH_CODE = 4;
	static int LAST_CODE_LENGTH_CODE = 19;

	static int[] lengthBases = [
		3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 
		31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258
	] ;	
	static int[] extraLengthBits = [
		0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 
		3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0,
	];
	static int[] distanceBases = [
		1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129,
		193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097,
		6145, 8193, 12289, 16385, 24577,
	];
	static int[] extraDistanceBits = [
		0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7,  7,
		8,  8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13,
	];	


this(PngDecodingDataStream stream) {
	this.stream = stream;
	readHeader = false;
	isLastBlock = false;
}

void setWindowSize(int windowSize) {
	window = new byte[windowSize];
}	

void readNextBlockHeader() {
	isLastBlock = stream.getNextIdatBit() != 0;
	compressionType = cast(byte)(stream.getNextIdatBits(2) & 0xFF);
	if (compressionType > 2) stream.error();	
	
	if (compressionType == UNCOMPRESSED) {
		byte b1 = stream.getNextIdatByte();
		byte b2 = stream.getNextIdatByte();
		byte b3 = stream.getNextIdatByte();
		byte b4 = stream.getNextIdatByte();
		if (b1 != ~b3 || b2 != ~b4) stream.error();
		uncompressedBytesRemaining = (b1 & 0xFF) | ((b2 & 0xFF) << 8);
	} else if (compressionType == COMPRESSED_DYNAMIC) {
		huffmanTables = PngHuffmanTables.getDynamicTables(stream);
	} else {
		huffmanTables = PngHuffmanTables.getFixedTables();
	}
}

byte getNextByte() {
	if (compressionType == UNCOMPRESSED) {
		if (uncompressedBytesRemaining == 0) {
			readNextBlockHeader();
			return getNextByte();
		}
		uncompressedBytesRemaining--;
		return stream.getNextIdatByte();
	} else {
		byte value = getNextCompressedByte();
		if (value == END_OF_COMPRESSED_BLOCK) {
			if (isLastBlock) stream.error();
			readNextBlockHeader();
			return getNextByte();
		} else {
			return value;
		}
	}
}

private void assertBlockAtEnd() {
	if (compressionType == UNCOMPRESSED) {
		if (uncompressedBytesRemaining > 0) stream.error();
	} else if (copyBytesRemaining > 0 ||
		(huffmanTables.getNextLiteralValue(stream) != END_OF_COMPRESSED_BLOCK)) 
	{
		stream.error();		
	}
}
void assertCompressedDataAtEnd() {
	assertBlockAtEnd();		
	while (!isLastBlock) {
		readNextBlockHeader();
		assertBlockAtEnd();
	}	
}

private byte getNextCompressedByte() {
	if (copyBytesRemaining > 0) {
		byte value = window[copyIndex];
		window[windowIndex] = value;
		copyBytesRemaining--;
		
		copyIndex++;
		windowIndex++;		
		if (copyIndex == window.length) copyIndex = 0;
		if (windowIndex == window.length) windowIndex = 0;

		return value;		
	}
	
	int value = huffmanTables.getNextLiteralValue(stream);
	if (value < END_OF_COMPRESSED_BLOCK) {
		window[windowIndex] = cast(byte) (value & 0xFF);
		windowIndex++;
		if (windowIndex >= window.length) windowIndex = 0;
		return cast(byte) (value & 0xFF);		
	} else if (value == END_OF_COMPRESSED_BLOCK) {
		readNextBlockHeader();
		return getNextByte();
	} else if (value <= LAST_LENGTH_CODE) {
		int extraBits = extraLengthBits[value - FIRST_LENGTH_CODE];
		int length = lengthBases[value - FIRST_LENGTH_CODE];
		if (extraBits > 0) {
			length += stream.getNextIdatBits(extraBits);
		}
		
		value = huffmanTables.getNextDistanceValue(stream);
		if (value > LAST_DISTANCE_CODE) stream.error();
		extraBits = extraDistanceBits[value];
		int distance = distanceBases[value];
		if (extraBits > 0) {
			distance += stream.getNextIdatBits(extraBits);
		}
		
		copyIndex = windowIndex - distance;
		if (copyIndex < 0) copyIndex += window.length;

		copyBytesRemaining = length;
		return getNextCompressedByte();
	} else {
		stream.error();
		return 0;
	}
}
	
}



class PngPlteChunk : PngChunk {

this(byte[] reference){
	super(reference);
}

/**
 * Get the number of colors in this palette.
 */
int getPaletteSize() {
	return getLength() / 3;
}

/**
 * Get a PaletteData object representing the colors
 * stored in this PLTE chunk.
 * The result should be cached as the PLTE chunk
 * does not store the palette data created.
 */
PaletteData getPaletteData() {
	DWTRGB[] rgbs = new DWTRGB[getPaletteSize()];
//	int start = DATA_OFFSET;
//	int end = DATA_OFFSET + getLength();
	for (int i = 0; i < rgbs.length; i++) {
		int offset = DATA_OFFSET + (i * 3);
		int red = reference[offset] & 0xFF;
		int green = reference[offset + 1] & 0xFF;
		int blue = reference[offset + 2] & 0xFF;
		rgbs[i] = new DWTRGB(red, green, blue);		
	}
	return new PaletteData(rgbs);
}

/**
 * Answer whether the chunk is a valid PLTE chunk.
 */
void validate(PngFileReadState readState, PngIhdrChunk headerChunk) {
	// A PLTE chunk is invalid if no IHDR has been read or if any PLTE,
	// IDAT, or IEND chunk has been read.
	if (!readState.readIHDR
		|| readState.readPLTE
		|| readState.readTRNS
		|| readState.readBKGD
		|| readState.readIDAT
		|| readState.readIEND) 
	{
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	} else {
		readState.readPLTE = true;
	}
	
	super.validate(readState, headerChunk);
	
	// Palettes cannot be included in grayscale images.
	if (!headerChunk.getCanHavePalette()) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	
	// Palette chunks' data fields must be event multiples
	// of 3. Each 3-ubyte group represents an RGB value.
	if (getLength() % 3 != 0) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);	
	
	// Palettes cannot have more entries than 2^bitDepth
	// where bitDepth is the bit depth of the image given
	// in the IHDR chunk.
	if (Compatibility.pow2(headerChunk.getBitDepth()) < getPaletteSize()) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
	
	// Palettes cannot have more than 256 entries.
	if (256 < getPaletteSize()) DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
}

void contributeToString(char[] buffer) {
	buffer ~= ("\n\tPalette size:");
	buffer ~= getPaletteSize();
}

}




public class PngTrnsChunk : PngChunk {
	static int TRANSPARENCY_TYPE_PIXEL = 0;
	static int TRANSPARENCY_TYPE_ALPHAS = 1;
	
this(byte[] reference){
	super(reference);
}

void validateLength(PngIhdrChunk header, PngPlteChunk paletteChunk) {
	boolean valid;
	switch (header.getColorType()) {
		case PngIhdrChunk.COLOR_TYPE_RGB:
			// Three 2-ubyte values (RGB)
			valid = getLength() == 6;
			break;
		case PngIhdrChunk.COLOR_TYPE_PALETTE:
			// Three 2-ubyte values (RGB)
			valid = getLength() <= paletteChunk.getLength();
			break;
		case PngIhdrChunk.COLOR_TYPE_GRAYSCALE:
			// One 2-ubyte value
			valid = getLength() == 2;
			break;
		// Cannot use both Alpha and tRNS
		case PngIhdrChunk.COLOR_TYPE_RGB_WITH_ALPHA:
		case PngIhdrChunk.COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
		default:
			valid = false;
	}
	if (!valid) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
}

/**
 * Answer whether the chunk is a valid tRNS chunk.
 */
void validate(PngFileReadState readState, PngIhdrChunk headerChunk, PngPlteChunk paletteChunk) {
	if (!readState.readIHDR
		|| (headerChunk.getMustHavePalette() && !readState.readPLTE)
		|| readState.readIDAT
		|| readState.readIEND) 
	{
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	} else {
		readState.readTRNS = true;
	}
	
	validateLength(headerChunk, paletteChunk);
	
	super.validate(readState, headerChunk);
}


int getTransparencyType(PngIhdrChunk header) {
	if (header.getColorType() == PngIhdrChunk.COLOR_TYPE_PALETTE) {
		return TRANSPARENCY_TYPE_ALPHAS;
	}
	return TRANSPARENCY_TYPE_PIXEL;
}

/**
 * Answer the transparent pixel RGB value.
 * This is not valid for palette color types.
 * This is not valid for alpha color types.
 * This will convert a grayscale value into
 * a palette index.
 * It will compress a 6 ubyte RGB into a 3 ubyte
 * RGB.
 */
int getDwtTransparentPixel(PngIhdrChunk header) {
	switch (header.getColorType()) {
		case PngIhdrChunk.COLOR_TYPE_GRAYSCALE:
			int gray = ((reference[DATA_OFFSET] & 0xFF) << 8)
				+ (reference[DATA_OFFSET + 1] & 0xFF);
			if (header.getBitDepth() > 8) {
				return PNGFileFormat.compress16BitDepthTo8BitDepth(gray);
			}
			return gray & 0xFF;
		case PngIhdrChunk.COLOR_TYPE_RGB:
			int red = ((reference[DATA_OFFSET] & 0xFF) << 8)
				| (reference[DATA_OFFSET + 1] & 0xFF);
			int green = ((reference[DATA_OFFSET + 2] & 0xFF) << 8)
				| (reference[DATA_OFFSET + 3] & 0xFF);
			int blue = ((reference[DATA_OFFSET + 4] & 0xFF) << 8)
				| (reference[DATA_OFFSET + 5] & 0xFF);			
			if (header.getBitDepth() > 8) {
				red = PNGFileFormat.compress16BitDepthTo8BitDepth(red);
				green = PNGFileFormat.compress16BitDepthTo8BitDepth(green);
				blue = PNGFileFormat.compress16BitDepthTo8BitDepth(blue);			
			}
			return (red << 16) | (green << 8) | blue;	
		default:
			DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
			return -1;
	}
	return -1;
}

/**
 * Answer an array of Alpha values that correspond to the 
 * colors in the palette.
 * This is only valid for the COLOR_TYPE_PALETTE color type.
 */
byte[] getAlphaValues(PngIhdrChunk header, PngPlteChunk paletteChunk) {
	if (header.getColorType() != PngIhdrChunk.COLOR_TYPE_PALETTE) {
		DWT.error(__FILE__, __LINE__, DWT.ERROR_INVALID_IMAGE);
	}
	byte[] alphas = new byte[paletteChunk.getPaletteSize()];
	int dataLength = getLength();
	int i = 0;
	for (i = 0; i < dataLength; i++) {
		alphas[i] = reference[DATA_OFFSET + i];
	}
	/**
	 * Any palette entries which do not have a corresponding
	 * alpha value in the tRNS chunk are spec'd to have an 
	 * alpha of 255.
	 */
	for (int j = i; j < alphas.length; j++) {
		alphas[j] = cast(byte) 255;
	}
	return alphas;
}
}
